package Busquedas;

import java.util.ArrayList;
import Problemas.Problema;

public class EnfriamientoSimulado extends Busqueda {

	protected int T;
	protected int enfriamiento;

	
	/**Constructora de la clase.
	 temperaratura es el valor  que indica en que medida puede ser aceptada una solucion
     enfriamento es el parametro que reduce la temperatura
	 */
	public EnfriamientoSimulado(Problema p,int temperatura,int variacionT) {
		super(p);
		T=temperatura;
		enfriamiento=variacionT;
	}


	@Override
	public ArrayList<Estado> buscar(boolean controlCiclos){
		Nodo ini=this.getNodo();
		controlCiclos=false;
		expandidos.add(ini.getEstado());
		Nodo mejorHastaAhora=ini;
		Nodo n=ini;
		n.setValorHeuristico(this.getProblema().funcionHeuristica(n.getEstado()));
		int op=0;
		boolean solucionado=false;
		long tiempo1=System.currentTimeMillis();
		try{	
			while((op!=-1)&&(!solucionado)){
				contadorNodos++;
				if (contadorNodos>maxContadorNodos) return null;
				op=escogeOperadorRandom(n);
				if (op!=-1){
					Estado e=this.getProblema().aplicaOperador(n.getEstado(), op);
					n.setOperadoresAplicados(op);
					if (e!=null){
						if (!(controlCiclos && estaEnExpandidos(e))){
							Nodo hijo=n.anadeHijo(op,e,this.getProblema().getCosteOperador(op));
							hijo.setValorHeuristico(this.getProblema().funcionHeuristica(hijo.getEstado()));
							int incrementoE=hijo.getValorHeuristico()-n.getValorHeuristico();
							if(incrementoE<=0){//si mejora la situacion se acepta el nodo
                                n=hijo;
								n.setValorHeuristico(this.getProblema().funcionHeuristica(n.getEstado()));
								expandidos.add(n.getEstado());
                                if(n.getValorHeuristico()<mejorHastaAhora.getValorHeuristico()){
                                    mejorHastaAhora=n;
                                }
                            }
                            else{
                                if(valido(incrementoE)){//Explora la posibilidad de pasar a un estado peor
                                    n=hijo;
                                    n.setValorHeuristico(this.getProblema().funcionHeuristica(n.getEstado()));
                                    expandidos.add(n.getEstado());
                                    }
                            }
						}
						enfriar();
					}
					if (this.getProblema().estadoFinal(mejorHastaAhora.getEstado()))
					{
						solucionado=true;
					}
				}		
			}
		}catch(OutOfMemoryError e){
			abiertos = null;
			camino=null;
			return null;
		}
		if (this.getProblema().estadoFinal(mejorHastaAhora.getEstado()))
		{
			solucionado=true;
			long tiempo2=System.currentTimeMillis();
			this.setTiempoTotal((tiempo2-tiempo1));
			generaCamino(mejorHastaAhora);
			return camino;
		}else camino=null;
		return camino;
	}

	
	/**
	 * Escoge uno de los operadores aleatoreamente
	 */
	private int escogeOperadorRandom(Nodo n){
		int [] operadoresPorAplicar=new int [n.getNumMaxHijos()];
		int numOperLibres=0;
		for (int i=0;i<n.getNumMaxHijos();i++){
			if (n.getOperadoresAplicados()[i]!=1){
				operadoresPorAplicar[numOperLibres]=i;
				numOperLibres++;
			}
		}
		int op=(int) (Math.random()*numOperLibres);
		if (numOperLibres!=0)return operadoresPorAplicar[op];
		else return -1;

	}
	
	
	
	/**
    Comprueba la condicion que el estado siguiente sea valido,a mayor temperatura
    mayor probabilidad de aceptación tendremos
	 */
	private boolean valido(int incrementoE){
		if (incrementoE<=0){
			return true;
		}
		else {
			double N=Math.random();
			double P;
			if (T>0){
				P=Math.pow(Math.E,(-incrementoE)/T);
			}
			else P=0;
			if (N<P){
				return true;
			}
			else {
				return false;
			}
		}		
	}
	
	


	private void enfriar(){
		if(T-enfriamiento<0)
            T=0;
        else
            T=T-enfriamiento;
	}

}
